home *** CD-ROM | disk | FTP | other *** search
/ Software of the Month Club 1996 September / Software of the Month Club 1996 September.iso / mac / Software Research Institute-SRI / Business / Alpha ƒ / Tcl / SystemCode / latexEngine.tcl < prev    next >
Encoding:
Text File  |  1995-12-03  |  31.1 KB  |  1,005 lines  |  [TEXT/ALFA]

  1. #############################################################################
  2. #############################################################################
  3. #
  4. # latexEngine.tcl (called from latex.tcl)
  5. #
  6. # The LaTeX engine
  7. #
  8. #############################################################################
  9. #
  10. # Author:  Tom Scavo <trscavo@syr.edu>
  11. #
  12. #############################################################################
  13. #############################################################################
  14.  
  15.  
  16. #--------------------------------------------------------------------------
  17. # Mark Menu:
  18. #--------------------------------------------------------------------------
  19.  
  20. # proc TeXMarkFile {} {
  21. #     set end [maxPos]
  22. #     set pos 0
  23. #     set l {}
  24. #     # Remove all previous marks in this file?
  25. #     set exp {\\((sub)*section|chapter)(\[.*\]|\*)?{([^{}]*)}}
  26. #     while {![catch {search -s -f 1 -r 1 -m 0 -i 0 $exp $pos} res]} {
  27. #         set start [lindex $res 0]
  28. #         set end [lindex $res 1]
  29. #         set text [getText $start $end]
  30. #         if {[regexp {\{(.*)\}} $text dummy mtch]} {
  31. #             set lab ""
  32. #             if {[regexp {(sub)*section} $text title]} then {
  33. #                 append lab $l [format "%[expr [string length $title] - 7]\s" ""]
  34. #             } else {
  35. #                 set l {   }
  36. #             }
  37. #             # WTP 
  38. #             regsub -all "\[\ \r\t\]\+" $mtch { } mtch
  39. #             if {[string length $mtch] > 30} { set mtch "[string range $mtch 0 26]..." }
  40. #             #
  41. #             append lab $mtch
  42. #             regsub -all {[<(]} $lab {} lab
  43. #             regsub -all {[)>]} $lab {╚} lab
  44. #             regsub -all {[!^/]} $lab {|} lab
  45. #             setNamedMark $lab [lineStart [expr $start - 1]] $start $start
  46. #         }
  47. #         set pos [expr $end+1]
  48. #     }
  49. # }
  50.  
  51. # Bug:  won't add a section (in a different chapter) with the same name.
  52. # Bug:  should ignore comments, but doesn't.
  53. proc TeXMarkFile {} {
  54.     set end [maxPos]
  55.     set pos 0
  56.     set l {}
  57.  
  58.     # Remove all previous marks in this file?
  59. #     set exp {^[^%]*\\((sub)*section|chapter)(\[.*\]|\*)?{(.*)}}
  60.     set exp {^\\((sub)*section|chapter)(\[.*\]|\*)?{(.*)}}
  61.     while {![catch {search -s -f 1 -r 1 -m 0 -i 0 $exp $pos} res]} {
  62.         set start [lindex $res 0]
  63.         set end [lindex $res 1]
  64.         set text [getText $start $end]
  65.         if {[regexp {\{(.*)\}} $text dummy mtch]} {
  66.             set lab ""
  67.             if {[regexp {(sub)*section} $text title]} then {
  68.                 append lab $l [format "%[expr [string length $title] - 7]\s" ""]
  69.             } else {
  70.                 set l {   }
  71.             }
  72.             # WTP 
  73.             regsub -all "\[\ \r\t\]\+" $mtch { } mtch
  74.             if {[string length $mtch] > 30} { set mtch "[string range $mtch 0 26]..." }
  75.             #
  76.             append lab $mtch
  77.             regsub -all {[<(]} $lab {} lab
  78.             regsub -all {[)>]} $lab {╚} lab
  79.             regsub -all {[!^/]} $lab {|} lab
  80.             setNamedMark $lab [lineStart [expr $start - 1]] $start $start
  81.         }
  82.         set pos [expr $end+1]
  83.     }
  84. }
  85.  
  86. #--------------------------------------------------------------------------
  87. # Command-double-clicking:
  88. #--------------------------------------------------------------------------
  89. # In TeX mode, use Cmd-double-clicks to follow references and citations,
  90. # or open input files.
  91. #
  92. # (written by Tom Pollard and Andreas Amann)
  93. #
  94. # Remaining bugs:
  95. #   - searches are successful even if the pattern is commented out
  96.     
  97. proc TeXDblClick {from to} {
  98.     global TeXmodeVars TeXSearchPath
  99.  
  100.     # Reset the search path list, forcing it to be rebuilt.
  101.     # (It would improve responsiveness if the searchPath were only
  102.     # rebuilt as needed, perhaps at startup and when requested by the user.)
  103.     #
  104.     set TeXSearchPath {}
  105.     
  106.     set bibPat "\\bibliography\{"
  107.     set theBibPat "\\begin\{thebibliography"
  108.     set bibitemPat {\bibitem(\[[^\]*\])?\{}
  109.     set labelPat "\\label\{"
  110.      set bibTopPat {@([a-zA-Z]+)[\{\(][     ]*}
  111.     
  112.     # Extend selection to largest string delimited by commas or curly-braces
  113.     set text [string trim [eval getText [TeXExtendArg $from $to]]]
  114.  
  115.     # Quote regexp-active characters in case we use $text in a regexp search
  116.     set qtext [quoteExpr $text]
  117.     
  118.     # Set $cmd to TeX command for which the selection is an argument, but
  119.     # only if user clicked on a valid command argument.
  120.     set cmd {}
  121.     if {[set mtch [findCommandWithParts $from 0]] != ""} {
  122.         set beg [lindex $mtch 0]
  123.         set arg [lindex $mtch 2]
  124.         set end [lindex $mtch 3]
  125.         # Make sure the user clicked within a TeX argument and not
  126.         # on the command name itself
  127.         if {$from > $arg && $to < $end} {
  128.             set cmd [extractCommandName [getText $beg $arg]]
  129.         }
  130.     }
  131.     
  132.     # \cite, \nocite, etc.
  133.     # 
  134.     if {[lsearch -exact $TeXmodeVars(citeCommands) $cmd] >= 0} {
  135.         
  136.         # Check first for a \thebibliography environment:
  137.         if {![catch {search -f 0 -r 0 -m 0 -s $theBibPat [maxPos]} mtch]} {
  138.             set bibPos [lindex $mtch 1]
  139.             if {![catch {search -f 1 -r 1 -m 0 -s "$bibitemPat$qtext\}" $bibPos} mtch]} {
  140.                 pushMark
  141.                 eval select $mtch
  142.                 message "press <Ctl .> to return to original cursor position"
  143.                 return
  144.             }
  145.         }
  146.         
  147.         # ...otherwise get the bib file name(s) from a \bibliography command
  148.         if {![catch {search -f 0 -r 0 -s -m 0 $bibPat [maxPos]} mtch]} {
  149.             # Get ALL the bib file names:
  150.             set beg [lindex $mtch 1]
  151.             set end [matchIt "\{" $beg]
  152.             set fnames "[split [getText $beg $end] ,]"
  153.             # Check every file:
  154.             foreach fname $fnames {
  155.                 set filename [absolutePath "$fname.bib"]
  156.                 set searchResult [searchInFile $filename $bibTopPat$qtext 1]
  157.                 if {[lindex $searchResult 0] >= 0} {
  158.                     openFileQuietly $filename
  159.                     eval select $searchResult
  160.                     return
  161.                 }    
  162.             }
  163.             # Build the TeX search path:
  164.             if {[llength $TeXSearchPath] == 0} {
  165.                 set TeXSearchPath [buildTeXSearchPath]
  166.             }
  167.             # Check every file in the TeX search path:
  168.             foreach fname $fnames {
  169.                 foreach folder $TeXSearchPath {
  170.                     set filename "$folder$fname.bib"
  171.                     set searchResult [searchInFile $filename $bibTopPat$qtext 1]
  172.                     if {[lindex $searchResult 0] >= 0} {
  173.                         openFileQuietly $filename
  174.                         eval select $searchResult
  175.                         return
  176.                     }    
  177.                 }
  178.             }
  179.             beep
  180.             message "can't find \"$text\" in the .bib file(s)"
  181.             
  182.         } else {
  183.             beep
  184.             message "can't find a \\bibliography"
  185.         }
  186.         
  187.     # \ref, \pageref, etc.
  188.     } elseif {[lsearch -exact $TeXmodeVars(refCommands) $cmd] >= 0} {
  189.         if {![catch {search -f 1 -r 0 -m 0 -s "$labelPat$text\}" 0} mtch]} {
  190.             pushMark
  191.             eval select $mtch
  192.             message "press <Ctl .> to return to original cursor position"
  193.         } else {
  194.             message {No matching \label found}
  195.         }
  196.         
  197.     #    \input
  198.     } elseif { $cmd == "input" } {
  199.         openTeXFile $text ".tex"
  200.  
  201.     #    \include
  202.     } elseif { $cmd == "include" } {
  203.         openTeXFile "${text}.tex"
  204.  
  205.     # \documentclass or \LoadClass
  206.     } elseif { $cmd == "documentclass" || $cmd == "LoadClass" } {
  207.         openTeXFile "${text}.cls"
  208.         
  209.     # \usepackage or \RequirePackage
  210.     } elseif { $cmd == "usepackage" || $cmd == "RequirePackage" } {
  211.         openTeXFile "${text}.sty"
  212.         
  213.     # \bibliography
  214.     } elseif {$cmd == "bibliography"} {
  215.         openTeXFile "${text}.bib"
  216.         
  217.     # \bibliographystyle
  218.     } elseif {$cmd == "bibliographystyle"} {
  219.         openTeXFile "${text}.bst"
  220.         
  221.     # box-making macro ($boxMacroName)
  222.     } elseif {$cmd == $TeXmodeVars(boxMacroName)} {
  223.         openTeXFile $text 
  224.         
  225.     # Other
  226.     } else {
  227.         select $from $to
  228.         message {Command-double-click on the required argument of an underlined LaTeX command}
  229.     }
  230. }
  231.  
  232. # Open a TeX source file. 
  233. #
  234. proc openTeXFile {file {ext ""}} {
  235.     global TeXSearchPath
  236.     # Determine absolute file specification
  237.     # Ignore $ext if $file already has an extension
  238.     if {[string length [file extension $file]] == 0} {
  239.         set file $file$ext
  240.     }
  241.     set filename [absolutePath $file]
  242.     if {![catch {openFileQuietly $filename}]} { 
  243.         message $filename
  244.         return 
  245.     }
  246.     if {[llength $TeXSearchPath] == 0} {
  247.         set TeXSearchPath [buildTeXSearchPath]
  248.     }
  249.     foreach folder $TeXSearchPath {
  250. #         set filename "$folder$file$ext"
  251.         set filename "$folder$file"
  252.         if {![catch {openFileQuietly $filename}]} {
  253.             message $filename
  254.             return     
  255.         }
  256.     }
  257.     beep
  258.     message "can't find TeX input file \"$file\""
  259. }
  260.  
  261. # Return a list of folders in which to search for TeX input files, 
  262. # including the TeXInputs folder (if it exists) and any folders of 
  263. # the form "*input*" in the TeX application directory.  The current
  264. # folder is not included in the list.
  265. #
  266. # (The TeXInputs folder is assigned from the AppPaths submenu.)
  267. #
  268. proc buildTeXSearchPath {} {
  269.     global TeXInputs texPath
  270.     message "building TeX search path╔"
  271.     set folders {}
  272.     
  273.     # The local 'TeXInputs' folder:
  274.     if {[info exists TeXInputs] && [string length $TeXInputs] > 0} { 
  275.         set folders [concat $folders [list $TeXInputs]]
  276.         # Search subfolders one level deep:
  277.         set folders [concat $folders [listSubfolders $TeXInputs 1]]
  278.     }
  279.  
  280.     # Any "*inputs*" folders in the TeX application folder:
  281.     if {[info exists texPath] && [string length $texPath] > 0} { 
  282.         set TeXDir [file dirname $texPath]
  283.         set folders [concat $folders [list $TeXDir]]
  284.         # Bug:  'glob' is case sensitive!
  285.         foreach folder [glob "$TeXDir:*\[Ii\]nputs*"] {
  286.             set folders [concat $folders [list $folder]]
  287.             # Search subfolders one level deep:
  288.             set folders [concat $folders [listSubfolders $folder 1]]
  289.         }
  290.     }
  291.  
  292.     # Make sure each folder ends with a colon:
  293.     set result {}
  294.     foreach folder $folders {
  295.         set folder "[string trimright $folder {:}]:"
  296.         set result [concat $result [list $folder]]
  297.     }
  298.     return $result
  299. }
  300.  
  301.  
  302. # Extend the argument around the position $from.
  303. # (Args are delimited by commas or curly-braces.)
  304. proc TeXExtendArg {from {to 0}} {
  305.     if {$to == 0} { set to $from }
  306.     set result [list $from $to]
  307.     if {![catch {search -f 0 -r 1 -s -m 0 "\[,\{\]" $from} mtch0]} {
  308.         if {![catch {search -f 1 -r 1 -s -m 0 "\[,\}\]" $to} mtch1]} {
  309.             set from [lindex $mtch0 1]
  310.             set to [lindex $mtch1 0]
  311.             ## Embedded braces in the arg probably mean that the user
  312.             ## clicked outside a valid command argument
  313.             if {[regexp "\[\{\}\]" [getText $from $to]] == 0} {
  314.                 set result [list $from $to]
  315.             }
  316.         }
  317.     }
  318.     return $result
  319. }
  320.  
  321. # Find a LaTeX command with arguments in either direction.
  322. # (see findCommandWithArgs in latexMacros.tcl)
  323. # This version returns the positions at which the command options 
  324. # and arguments start, as well.
  325. proc findCommandWithParts {pos direction} {
  326.     set searchString {\\([^a-zA-Z\t\r]|[a-zA-Z]+\*?)(\[.*\])*({[^{}]*})?}
  327.     if {![catch {search -s -f $direction -r 1 $searchString $pos} mtch]} {
  328.         set beg [lindex $mtch 0]
  329.         set end [lindex $mtch 1]
  330.         regexp -indices $searchString [getText $beg $end] all cmd opt arg
  331.         set opt [expr $beg + [lindex $opt 0]]
  332.         set arg [expr $beg + [lindex $arg 0]]
  333.         return [list $beg $opt $arg $end]
  334.     } else {
  335.         return ""
  336.     }
  337. }
  338.  
  339. #--------------------------------------------------------------------------
  340. # Insertion routines:
  341. #--------------------------------------------------------------------------
  342.  
  343. # Returns a modified text string if the string $text is non-null, 
  344. # and the null string otherwise.  The argument 'operation' is a 
  345. # string directing 'doPrefixText' to either "insert" or "remove" 
  346. # $prefixString to/from each line of $text.  (This routine is
  347. # adapted from strings.tcl.)
  348. proc doPrefixText {operation prefixString text} {
  349.     if {$text == ""} {return ""}
  350.     set pref [quoteExpr $prefixString]
  351.     if {$operation == "insert"} then {
  352.         set trailChar ""
  353.         set textLen [string length $text]
  354.         if {[string index $text [expr $textLen-1]] == "\r"} then {
  355.             set text [string range $text 0 [expr $textLen-2]]
  356.             set trailChar "\r"
  357.         }
  358.         set str \r$prefixString
  359.         regsub -all \r $text $str text
  360.         return $prefixString$text$trailChar
  361.     } elseif {$operation == "remove"} then {
  362.         regsub -all \r$pref $text \r text
  363.         regsub ^$pref $text "" text
  364.         return $text
  365.     }
  366. }
  367.  
  368. # Shift each line of $text to the right by inserting a string of
  369. # $whitespace characters at the beginning of each line, returning
  370. # the resulting string.
  371. proc shiftTextRight {text whitespace} {
  372.     return [doPrefixText "insert" $whitespace $text]
  373. }
  374.  
  375. # Return a string of whitespace characters from the beginning 
  376. # of the line containing $pos up to the first non-whitespace 
  377. # character.
  378. proc getIndentation {pos} {
  379.     set text [getText [lineStart $pos] [nextLineStart $pos]]
  380.     regexp {^[ \t]*} $text theIndentation
  381.     return $theIndentation
  382. }
  383.  
  384. # Return an "indented carriage return" if any character preceding 
  385. # the insertion point (on the same line) is a non-whitespace 
  386. # character.  Otherwise, return the null string.
  387. proc openingCarriageReturn {} {
  388.     set pos [getPos]
  389.     set end $pos
  390.     set start [lineStart $pos]
  391.     set text [getText $start $end]
  392.     if {[isWhitespace $text]} then {
  393.         if {$start == $end} {return [getIndentation $pos]} {return ""}
  394.     } else {
  395.         return "\r[getIndentation $pos]"
  396.     }
  397. }
  398. # Return an "indented carriage return" if any character following 
  399. # the insertion point (on the same line) is a non-whitespace 
  400. # character.  Otherwise, return the null string.
  401. proc closingCarriageReturn {} {
  402.     set pos [selEnd]
  403.     if {[isSelection] && ($pos == [lineStart $pos])} then {
  404.         return "\r"
  405.     } else {
  406.         set start $pos
  407.         set end [nextLineStart $start]
  408.         set text [getText $start $end]
  409.         if {[isWhitespace $text]} then {
  410.             return ""
  411.         } else {
  412.             return "\r[getIndentation $pos]"
  413.         }
  414.     }
  415. }
  416.  
  417. # Insert an object at the insertion point. If there is a selection and the 
  418. # global variable 'deleteObjectNoisily' is false, quietly delete the selection 
  419. # (like 'paste'). Otherwise, prompt the user for the appropriate action. 
  420. # Returns true if the object is ultimately inserted, and false if the 
  421. # user cancels the operation. 
  422. proc insertObject {objectName} {
  423.     global TeXmodeVars
  424. #     if {$TeXmodeVars(expertMode) == 0} then {
  425. #         if {![isInDocument]} then {
  426. #             if {$TeXmodeVars(searchNoisily)} {beep}
  427. #             case [askyesno "The cursor is not in the document environment.\
  428. #                             \rContinue the operation?"] in {
  429. #                 "yes" {}
  430. #                 "no" {error "insertObject:  out of context"}
  431. #             }
  432. #         }
  433. #     }
  434.     if {[isSelection]} then {
  435.         if {$TeXmodeVars(deleteObjectNoisily)} then {
  436.             case [askyesno -c "Delete selection?"] in {
  437.                 "yes" {deleteText [getPos] [selEnd]}
  438.                 "no" {backwardChar}
  439.                 "cancel" {return 0}
  440.             }
  441.         } else {
  442.             deleteText [getPos] [selEnd]
  443.         }
  444.     }
  445.     insertText $objectName
  446.     return 1
  447. }
  448.  
  449. # Insert an object at the insertion point. If there is a selection, wrap 
  450. # it inside the parameters $left and $right. Returns true if there is a 
  451. # selection (in which case it will wrap), and false otherwise. 
  452. proc wrapObject {left right} {
  453. #     global TeXmodeVars
  454. #     if {$TeXmodeVars(expertMode) == 0} then {
  455. #         if {![isInDocument]} then {
  456. #             if {$TeXmodeVars(searchNoisily)} {beep}
  457. #             case [askyesno "The cursor is not in the document environment.\
  458. #                             \rContinue the operation?"] in {
  459. #                 "yes" {}
  460. #                 "no" {error "wrapObject:  out of context"}
  461. #             }
  462. #         }
  463. #     }
  464.     set currentPos [getPos]
  465.     set selected [isSelection]
  466.     if {$selected} then {
  467.         replaceText $currentPos [selEnd] $left [getSelect] $right
  468.     } else {
  469.         insertText $left "Ñ" $right
  470.     }
  471.     goto $currentPos
  472.     nextTabStop
  473.     return $selected
  474. }
  475.  
  476. # Builds and returns a LaTeX environment, that is, a \begin...\end 
  477. # pair, given the name of the environment, an argument string, 
  478. # and the environment body.  The body should be passed to this 
  479. # procedure fully formatted, including indentation.
  480. proc buildEnvironment {envName envArg envBody trailingComment} {
  481.     set indent [getIndentation [getPos]]
  482.     set envStr [openingCarriageReturn]
  483.     append envStr "\\begin{" $envName "}"
  484.     append envStr $envArg "\r"
  485.     append envStr $envBody
  486.     append envStr $indent "\\end{" $envName "}$trailingComment"
  487.     append envStr [closingCarriageReturn]
  488.     return $envStr
  489. }
  490.  
  491. # Inserts a LaTeX environment with the specified name, argument, 
  492. # and body at the insertion point.  Positions the cursor at the 
  493. # beginning of the environment, leaving any subsequent action to the 
  494. # calling procedure.  Deletes the current selection quietly if the 
  495. # global variable 'deleteEnvironmentNoisily' is false; otherwise 
  496. # the user is prompted for directions.  Returns true if the 
  497. # environment is ultimately inserted, and false if the user cancels 
  498. # the operation.
  499. proc insertEnvironment {envName envArg envBody} {
  500.     global TeXmodeVars
  501. #     if {$TeXmodeVars(expertMode) == 0} then {
  502. #         if {![isInDocument]} then {
  503. #             if {$TeXmodeVars(searchNoisily)} {beep}
  504. #             case [askyesno "The cursor is not in the document environment.\
  505. #                             \rContinue the operation?"] in {
  506. #                 "yes" {}
  507. #                 "no" {error "insertEnvironment:  out of context"}
  508. #             }
  509. #         }
  510. #     }
  511.     if {[isSelection]} then {
  512.         if {$TeXmodeVars(deleteEnvironmentNoisily)} then {
  513.             case [askyesno -c "Delete selection?"] in {
  514.                 "yes" {}
  515.                 "no" {backwardChar}
  516.                 "cancel" {return 0}
  517.             }
  518.         }
  519.     }
  520.     set start [getPos]
  521.     set end [selEnd]
  522.     set body [shiftTextRight $envBody [getIndentation $start]]
  523.     replaceText $start $end [buildEnvironment $envName $envArg $body "Ñ"]
  524.     goto $start
  525.     return 1
  526. }
  527.  
  528. # Inserts an environment with the given name, argument, and body at 
  529. # the insertion point.  Positions the cursor at the beginning of 
  530. # the environment, leaving any subsequent action to the calling 
  531. # procedure.  If there is currently a selection, cut and paste it 
  532. # into the body of the new environment, maintaining proper 
  533. # indentation; otherwise, insert a tab stop into the body of the
  534. # environment.  Returns true if there is a selection, and false 
  535. # otherwise.
  536. proc wrapEnvironment {envName envArg envBody} {
  537. #     global TeXmodeVars
  538. #     if {$TeXmodeVars(expertMode) == 0} then {
  539. #         if {![isInDocument]} then {
  540. #             if {$TeXmodeVars(searchNoisily)} {beep}
  541. #             case [askyesno "The cursor is not in the document environment.\
  542. #                             \rContinue the operation?"] in {
  543. #                 "yes" {}
  544. #                 "no" {error "wrapEnvironment:  out of context"}
  545. #             }
  546. #         }
  547. #     }
  548.     set start [getPos]
  549.     set end [selEnd]
  550.     set indent [getIndentation $start]
  551.     if {[isSelection]} then {
  552.         set text [getSelect]
  553.         set textLen [string length $text]
  554.         if {[string index $text [expr $textLen-1]] != "\r"} then {
  555.             append text "\r"
  556.         }
  557.         if {$start == [lineStart $start]} then {
  558.             set body [shiftTextRight $text \t]
  559.         } else {
  560.             set body "$indent[shiftTextRight $text \t]"
  561.         }
  562.         append body [shiftTextRight $envBody $indent]
  563.         set environment [buildEnvironment $envName $envArg $body "Ñ"]
  564.         set returnFlag 1
  565.     } else {
  566.         append body "$indent\tÑ\r" [shiftTextRight $envBody $indent]
  567.         set environment [buildEnvironment $envName $envArg $body "Ñ"]
  568.         set returnFlag 0
  569.     }
  570.     replaceText $start $end $environment
  571.     goto $start
  572.     return $returnFlag
  573. }
  574.  
  575. # A generic call to 'wrapEnvironment' used throughout latex.tcl:
  576. proc doWrapEnvironment {envName} {
  577.     if {[wrapEnvironment $envName "" ""]} then {
  578.         set msgText "selection wrapped"
  579.     } else {
  580.         set msgText "enter body of $envName environment"
  581.     }
  582.     nextTabStop
  583.     message $msgText
  584. }
  585.  
  586. # Inserts a structured document template at the insertion point.  
  587. # Three arguments are required:  the class name of the document, a 
  588. # preamble string, and a string containing the body of the document.  
  589. # If the preamble is null, a generic \usepackage statement is 
  590. # inserted; otherwise, the preamble is inserted as is.  This routine 
  591. # does absolutely no error-checking (this is totally left up to the 
  592. # calling procedure) and returns nothing.
  593. proc insertDocument {className preamble docBody} {
  594.     set docStr "\\documentclass\[Ñ\]{$className}\r"
  595.     if {$preamble == ""} then {
  596.         append docStr "\\usepackage\[Ñ\]{Ñ}\r\rÑ\r\r"
  597.     } else {
  598.         append docStr $preamble
  599.     }
  600.     append docStr [buildEnvironment "document" "" $docBody "\r"]
  601.     set start [getPos]
  602.     set end [selEnd]
  603.     replaceText $start $end $docStr
  604.     goto $start
  605.     return
  606. }
  607.  
  608. # Inserts a document template at the insertion point given the 
  609. # class name of the document to be inserted.  If ALL of the current
  610. # document is selected, then the routine wraps the text inside a
  611. # generic document template.  If the file is empty, a bullet is 
  612. # inserted in place of the document body.  If neither of these 
  613. # conditions is true, an alert is displayed, and no action is taken.  
  614. # Returns true if wrapping occurs, and false otherwise.
  615. proc wrapDocument {className} {
  616.     if {[isEmptyFile]} then {
  617.         append body "\rÑ\r\r"
  618.         set returnFlag 0
  619.     } else {
  620.         if {[isDocumentSelected]} then {
  621.             set text [getSelect]
  622.             append body "\r$text\r"
  623.             set returnFlag 1
  624.         } else {
  625.             alertnote "nonempty file:  delete text or \'Select All\'\
  626.                 from the Edit menu"
  627.             return 0
  628.         }
  629.     }
  630.     set docStr "\\documentclass\[Ñ\]{$className}\r"
  631.     append docStr "\\usepackage\[Ñ\]{Ñ}\r\rÑ\r\r"
  632.     append docStr [buildEnvironment "document" "" $body "\r"]
  633.     set start [getPos]
  634.     set end [selEnd]
  635.     replaceText $start $end $docStr
  636.     goto $start
  637.     return $returnFlag
  638. }
  639.  
  640. #--------------------------------------------------------------------------
  641. # Booleans to determine the location of the insertion point
  642. #--------------------------------------------------------------------------
  643.  
  644. # Return true if the insertion point is before the preamble, and 
  645. # false otherwise.   Define "before the preamble" to be all text to 
  646. # the left of "\" in "\documentclass".
  647. proc isBeforePreamble {} {
  648.     set searchString "\\documentclass"
  649.     set searchResult [search -s -f 1 -r 0 -n $searchString [getPos]]
  650.     if {[llength $searchResult]} {
  651.         return 1
  652.     } else {
  653.         return 0
  654.     }
  655. }
  656.  
  657. # Return true if the insertion point is in the preamble, and false
  658. # otherwise.  Define "preamble" to be all text to the left of "\" 
  659. # in "\begin{document}", but not before the "\" in "\documentclass".
  660. proc isInPreamble {} {
  661.     set searchString "\\begin\{document\}"
  662.     set searchResult [search -s -f 1 -r 0 -n $searchString [getPos]]
  663.     if {[llength $searchResult]} {
  664.         return 1
  665.     } else {
  666.         return 0
  667.     }
  668. }
  669.  
  670. # Return true if the insertion point is in the document environment,
  671. # and false otherwise.  Define "document" to be all text between 
  672. # "\begin{document}" and "\end{document}", exclusive.
  673. proc isInDocument {} {
  674.     set pos [getPos]
  675.     set searchString "\\begin\{document\}"
  676.     # adjust for the length of the search string:
  677.     set len [string length $searchString]
  678.     if {$pos < $len} then {return 0}
  679.     set searchResult [search -s -f 0 -r 0 -n $searchString [expr $pos - $len]]
  680.     if {[llength $searchResult]} then {
  681.         set searchString "\\end\{document\}"
  682.         set searchResult [search -s -f 1 -r 0 -n $searchString $pos]
  683.         if {[llength $searchResult]} then {
  684.             return 1
  685.         } else {
  686.             return 0
  687.         }
  688.     } else {
  689.         return 0
  690.     }
  691. }
  692.  
  693. # Return true if the insertion point is after the document
  694. # environment, and false otherwise.  Define "after the document 
  695. # environment" to be all text to the right of "}" in "\end{document}".
  696. proc isAfterDocument {} {
  697.     set pos [getPos]
  698.     set searchString "\\end\{document\}"
  699.     set searchResult [search -s -f 0 -r 0 -n $searchString $pos]
  700.     if {[llength $searchResult]} {
  701.         set pos1 [lindex $searchResult 0]
  702.         set pos2 [lindex $searchResult 1]
  703.         if {$pos1 <= $pos && $pos < $pos2} then {
  704.             return 0
  705.         } else {
  706.             return 1
  707.         }
  708.     } else {
  709.         return 0
  710.     }
  711. }
  712.  
  713. proc isInMathMode {} {
  714.     set pos [getPos]
  715.     # Check to see if in LaTeX math mode:
  716.     set searchString {\\[()]}
  717.     set searchResult1 [search -s -f 0 -r 1 -n $searchString [expr $pos - 1]]
  718.     if {[llength $searchResult1]} then {
  719.         set delim1 [eval getText $searchResult1]
  720.     } else {
  721.         set delim1 "none"
  722.     }
  723.     set searchResult2 [search -s -f 1 -r 1 -n $searchString $pos]
  724.     if {[llength $searchResult2]} then {
  725.         set delim2 [eval getText $searchResult2]
  726.     } else {
  727.         set delim2 "none"
  728.     }
  729.     set flag1 [expr [string match {none} $delim1] && [string match {\\)} $delim2]]
  730.     set flag2 [expr [string match {\\(} $delim1] && [string match {none} $delim2]]
  731.     set flag3 [expr [string match {\\(} $delim1] && [string match {\\)} $delim2]]
  732.     set flag4 [expr [string match {\\(} $delim1] && [string match {\\(} $delim2]]
  733.     set flag5 [expr [string match {\\)} $delim1] && [string match {\\)} $delim2]]
  734.     if {$flag3} then {
  735.         return 1
  736.     } elseif {$flag1 || $flag2 || $flag4 || $flag5} then {
  737.         set messageString "unbalanced math mode delimiters"
  738.         beep
  739.         alertnote $messageString
  740.         error "isInMathMode:  $messageString"
  741.     }
  742.     # Check to see if in LaTeX displaymath mode:
  743.     set searchString {[^\\]\\\[|\\\]}
  744.     set searchResult1 [search -s -f 0 -r 1 -n $searchString [expr $pos - 1]]
  745.     if {[llength $searchResult1]} then {
  746.         set begPos [lindex $searchResult1 0]
  747.         set endPos [lindex $searchResult1 1]
  748.         if {[lookAt [expr $endPos - 1]] == "\["} then {
  749.             set delim1 [getText [expr $begPos + 1] $endPos]
  750.         } else {
  751.             set delim1 [eval getText $searchResult1]
  752.         }
  753.     } else {
  754.         set delim1 "none"
  755.     }
  756.     set searchResult2 [search -s -f 1 -r 1 -n $searchString $pos]
  757.     if {[llength $searchResult2]} then {
  758.         set begPos [lindex $searchResult2 0]
  759.         set endPos [lindex $searchResult2 1]
  760.         if {[lookAt [expr $endPos - 1]] == "\["} then {
  761.             set delim2 [getText [expr $begPos + 1] $endPos]
  762.         } else {
  763.             set delim2 [eval getText $searchResult2]
  764.         }
  765.     } else {
  766.         set delim2 "none"
  767.     }
  768.     set flag1 [expr [string match {none} $delim1] && [string match {\\\]} $delim2]]
  769.     set flag2 [expr [string match {\\\[} $delim1] && [string match {none} $delim2]]
  770.     set flag3 [expr [string match {\\\[} $delim1] && [string match {\\\]} $delim2]]
  771.     set flag4 [expr [string match {\\\[} $delim1] && [string match {\\\[} $delim2]]
  772.     set flag5 [expr [string match {\\\]} $delim1] && [string match {\\\]} $delim2]]
  773.     if {$flag3} then {
  774.         return 1
  775.     } elseif {$flag1 || $flag2 || $flag4 || $flag5} then {
  776.         set messageString "unbalanced math mode delimiters"
  777.         beep
  778.         alertnote $messageString
  779.         error "isInMathMode:  $messageString"
  780.     }
  781.     # Check to see if in math environment:
  782.     set envName [extractCommandArg [eval getText [searchEnvironment]]]
  783.     set envList [list "math" "displaymath" "equation" "eqnarray" "eqnarray*" "array"]
  784.     if {[lsearch -exact $envList $envName] == -1} {return 0} {return 1}
  785. }
  786.  
  787. #--------------------------------------------------------------------------
  788. # Routines to dissect LaTeX commands
  789. #--------------------------------------------------------------------------
  790.  
  791. # Given a LaTeX command string, extract and return the command name.
  792. proc extractCommandName {commandStr} {
  793.     if {[regexp {\\([^a-zA-Z\t\r]|[a-zA-Z]+\*?)} $commandStr dummy commandName]} then {
  794.         return $commandName
  795.     } else {
  796.         return ""
  797.     }
  798. }
  799.  
  800. # Given a LaTeX command string with at most one required argument and
  801. # no embedded carriage returns, extract and return the argument.
  802. # (Note:  this proc needs more testing.)
  803. proc extractCommandArg {commandStr} {
  804.     if {[regexp {\{(.*)\}} $commandStr dummy arg]} then {
  805.         return $arg
  806.     } else {
  807.         return ""
  808.     }
  809. }
  810.  
  811. #--------------------------------------------------------------------------
  812. # An environment search routine
  813. #--------------------------------------------------------------------------
  814.  
  815. # Search for the closest surrounding environment and return a list 
  816. # of two positions if the environment is found; otherwise return the 
  817. # empty list.  Assumes the LaTeX document is syntactically correct.   
  818. # The command 'eval select [searchEnvironment]' selects the "\begin" 
  819. # statement (with argument) of the surrounding environment.
  820. proc searchEnvironment {} {
  821.     watchCursor
  822.     set pos [getPos]
  823.     if {$pos == 0} {
  824.         return [list]
  825.     }
  826.     # adjust position if insertion point is contained in "\end{...}"
  827.     set searchPos [expr $pos - 1]
  828.     set searchString {\\end\{[^\{\}]*\}}
  829.     set searchResult [search -s -f 0 -r 1 -n $searchString $searchPos]
  830.     if {[llength $searchResult]} {
  831.         set pos1 [lindex $searchResult 0]
  832.         set pos2 [lindex $searchResult 1]
  833.         if {$pos1 < $pos && $pos < $pos2} then {
  834.             set searchPos [expr $pos1 - 1]
  835.         }
  836.     }
  837.     # begin reverse search:
  838.     set searchString {\\(begin|end)\{[^\{\}]*\}}
  839.     set searchResult [search -s -f 0 -r 1 -n $searchString $searchPos]
  840.     if {[llength $searchResult]} {
  841. #         set text [getText [lindex $searchResult 0] [lindex $searchResult 1]]
  842.         set text [eval getText $searchResult]
  843.     } else {
  844.         return [list]
  845.     }
  846.     set depthCounter 0
  847.     set commandName [extractCommandName $text]
  848.     while {$commandName == "end" || $depthCounter > 0} {
  849.         if {$commandName == "end"} {
  850.             set depthCounter [expr $depthCounter + 1]
  851.         } else {
  852.             set depthCounter [expr $depthCounter - 1]
  853.         }
  854.         set searchPos [expr [lindex $searchResult 0] - 1]
  855.         set searchResult [search -s -f 0 -r 1 -n $searchString $searchPos]
  856.         if {[llength $searchResult]} {
  857. #             set text [getText [lindex $searchResult 0] [lindex $searchResult 1]]
  858.             set text [eval getText $searchResult]
  859.         } else {
  860.             return [list]
  861.         }
  862.         set commandName [extractCommandName $text]
  863.     }
  864.     return $searchResult
  865. }
  866.  
  867. #--------------------------------------------------------------------------
  868. # Misc:
  869. #--------------------------------------------------------------------------
  870.  
  871. proc isInteger {str1} {
  872. #     if { [regexp {^(\+|-)?[0-9]+$} [string trim $str1]] } then {
  873. #         return 1
  874. #     } else {
  875. #         return 0
  876. #     }
  877.     return [regexp {^(\+|-)?[0-9]+$} [string trim $str1]]
  878. }
  879.  
  880. proc isUnsignedInteger {str1} {
  881. #     if { [regexp {^[0-9]+$} [string trim $str1]] } then {
  882. #         return 1
  883. #     } else {
  884. #         return 0
  885. #     }
  886.     return [regexp {^[0-9]+$} [string trim $str1]]
  887. }
  888.  
  889. proc isPositiveInteger {str1} {
  890.     if { [isUnsignedInteger $str1] } then {
  891.         if { ![regexp {^0+$} [string trim $str1]] } {
  892.             return 1
  893.         }
  894.     }
  895.     return 0
  896. }
  897.  
  898. # Checks to see if there is a current selection.
  899. #
  900. # This seems to be bugged:
  901. # proc isSelection {} {
  902. #     return [expr [getPos] == [selEnd]]
  903. # }
  904. proc isSelection {} {
  905.     return [string length [getSelect]]
  906. }
  907.  
  908. # Checks to see if the current window is empty, except for whitespace.
  909. proc isEmptyFile {} {
  910.     return [isWhitespace [getText 0 [maxPos]]]
  911. }
  912.  
  913. # If there is a selection, make sure it's uppercase.  Otherwise, 
  914. # check to see if the character after the insertion point is uppercase.
  915. proc isUppercase {} {
  916.     if {[isSelection]} then {
  917.         set text [getSelect]
  918.     } else {
  919.         set text [lookAt [getPos]]
  920.     }
  921.     return [expr {[string toupper $text] == $text}]
  922. }
  923.  
  924. # Returns true if the entire window is selected, and false otherwise.
  925. proc isDocumentSelected {} {
  926.     return [expr {([getPos] == 0) && ([selEnd] == [maxPos])}]
  927. }
  928.  
  929. # Takes any string and tests whether or not that string contains all 
  930. # whitespace characters.  Carriage returns are considered whitespace, 
  931. # as are spaces and tabs.  Also returns true for the null string.
  932. proc isWhitespace {anyString} {
  933.     set len [string length $anyString]
  934.     for {set i 0} {$i < $len} {incr i} {
  935.         set c [string index $anyString $i]
  936.         if {($c != "\ ") && ($c != "\t") && ($c != "\r")} then {return 0}
  937.     }
  938.     return 1
  939. }
  940.  
  941. # Select the line containing the insertion point.
  942. proc lineSelect {} {
  943.     goto [lineStart [getPos]]
  944.     nextLineSelect
  945. }
  946.  
  947. proc checkMathMode {procName mathModeFlag} {
  948. #     global TeXmodeVars
  949. #     if {$TeXmodeVars(expertMode) == 0} then {
  950. #         if {[isInDocument] && [isInMathMode] != $mathModeFlag} then {
  951. #             if {$TeXmodeVars(searchNoisily)} {beep}
  952. #             if {$mathModeFlag} then {
  953. #                 set messageString "You are not in math mode"
  954. #             } else {
  955. #                 set messageString "You are already in math mode"
  956. #             }
  957. #             case [askyesno "$messageString.\rContinue the operation?"] in {
  958. #                 "yes" {}
  959. #                 "no" {error "$procName:  out of context"}
  960. #             }
  961. #         }
  962. #     }
  963. }
  964.  
  965. # Check to see if the LaTeX symbol package has been loaded; if not, ask 
  966. # the user for directions.  Returns false if the package is not loaded
  967. # AND the user cancels the operation; otherwise, returns true.
  968. proc isSymbolPackageLoaded {} {
  969.     global searchNoisily
  970.     set begPos [getPos]
  971.     set endPos [selEnd]
  972.     set searchString {\\usepackage\{.*latexsym.*\}}
  973.     set searchResult [search -s -n -f 0 -m 0 -i 1 -r 1 $searchString $begPos]
  974.     if {[llength $searchResult] == 0} then {
  975.         case [askyesno -c "Insert the LaTeX symbol package?"] in {
  976.             "yes" {
  977.                 set searchString {\\documentclass(\[.*\])?\{.*\}}
  978.                 set searchResult [search -s -n -f 0 -m 0 -i 1 -r 1 $searchString $begPos]
  979.                 if {[llength $searchResult] == 0} then {
  980.                     set returnVal 0
  981.                     if {$searchNoisily} {beep}
  982.                     message "can't find \\documentclass"
  983.                 } else {
  984.                     goto [lindex $searchResult 1]
  985.                     set txt "\r\\usepackage\{latexsym\}"
  986.                     set offset [string length $txt]
  987.                     set begPos [expr $begPos + $offset]
  988.                     set endPos [expr $endPos + $offset]
  989.                     insertText $txt
  990.                     set returnVal 1
  991.                 }
  992.             }
  993.             "no" {set returnVal 1}
  994.             "cancel" {set returnVal 0}
  995.         }
  996.     } else {
  997.         set returnVal 1
  998.     }
  999.     select $begPos $endPos
  1000.     return $returnVal
  1001. }
  1002.  
  1003.  
  1004.